

;(function( $, window, document, undefined ) {

var dataPropertyName = "virtualMouseBindings",
	touchTargetPropertyName = "virtualTouchID",
	virtualEventNames = "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split( " " ),
	touchEventProps = "clientX clientY pageX pageY screenX screenY".split( " " ),
	mouseHookProps = $.event.mouseHooks ? $.event.mouseHooks.props : [],
	mouseEventProps = $.event.props.concat( mouseHookProps ),
	activeDocHandlers = {},
	resetTimerID = 0,
	startX = 0,
	startY = 0,
	didScroll = false,
	clickBlockList = [],
	blockMouseTriggers = false,
	blockTouchTriggers = false,
	eventCaptureSupported = "addEventListener" in document,
	$document = $( document ),
	nextTouchID = 1,
	lastTouchID = 0;

$.vmouse = {
	moveDistanceThreshold: 10,
	clickDistanceThreshold: 10,
	resetTimerDuration: 1500
};

function getNativeEvent( event ) {

	while ( event && typeof event.originalEvent !== "undefined" ) {
		event = event.originalEvent;
	}
	return event;
}

function createVirtualEvent( event, eventType ) {

	var t = event.type,
		oe, props, ne, prop, ct, touch, i, j;

	event = $.Event(event);
	event.type = eventType;

	oe = event.originalEvent;
	props = $.event.props;

	// addresses separation of $.event.props in to $.event.mouseHook.props and Issue 3280
	// https://github.com/jquery/jquery-mobile/issues/3280
	if ( t.search( /^(mouse|click)/ ) > -1 ) {
		props = mouseEventProps;
	}

	// copy original event properties over to the new event
	// this would happen if we could call $.event.fix instead of $.Event
	// but we don't have a way to force an event to be fixed multiple times
	if ( oe ) {
		for ( i = props.length, prop; i; ) {
			prop = props[ --i ];
			event[ prop ] = oe[ prop ];
		}
	}

	// make sure that if the mouse and click virtual events are generated
	// without a .which one is defined
	if ( t.search(/mouse(down|up)|click/) > -1 && !event.which ){
		event.which = 1;
	}

	if ( t.search(/^touch/) !== -1 ) {
		ne = getNativeEvent( oe );
		t = ne.touches;
		ct = ne.changedTouches;
		touch = ( t && t.length ) ? t[0] : ( (ct && ct.length) ? ct[ 0 ] : undefined );

		if ( touch ) {
			for ( j = 0, len = touchEventProps.length; j < len; j++){
				prop = touchEventProps[ j ];
				event[ prop ] = touch[ prop ];
			}
		}
	}

	return event;
}

function getVirtualBindingFlags( element ) {

	var flags = {},
		b, k;

	while ( element ) {

		b = $.data( element, dataPropertyName );

		for (  k in b ) {
			if ( b[ k ] ) {
				flags[ k ] = flags.hasVirtualBinding = true;
			}
		}
		element = element.parentNode;
	}
	return flags;
}

function getClosestElementWithVirtualBinding( element, eventType ) {
	var b;
	while ( element ) {

		b = $.data( element, dataPropertyName );

		if ( b && ( !eventType || b[ eventType ] ) ) {
			return element;
		}
		element = element.parentNode;
	}
	return null;
}

function enableTouchBindings() {
	blockTouchTriggers = false;
}

function disableTouchBindings() {
	blockTouchTriggers = true;
}

function enableMouseBindings() {
	lastTouchID = 0;
	clickBlockList.length = 0;
	blockMouseTriggers = false;

	// When mouse bindings are enabled, our
	// touch bindings are disabled.
	disableTouchBindings();
}

function disableMouseBindings() {
	// When mouse bindings are disabled, our
	// touch bindings are enabled.
	enableTouchBindings();
}

function startResetTimer() {
	clearResetTimer();
	resetTimerID = setTimeout(function(){
		resetTimerID = 0;
		enableMouseBindings();
	}, $.vmouse.resetTimerDuration );
}

function clearResetTimer() {
	if ( resetTimerID ){
		clearTimeout( resetTimerID );
		resetTimerID = 0;
	}
}

function triggerVirtualEvent( eventType, event, flags ) {
	var ve;

	if ( ( flags && flags[ eventType ] ) ||
				( !flags && getClosestElementWithVirtualBinding( event.target, eventType ) ) ) {

		ve = createVirtualEvent( event, eventType );

		$( event.target).trigger( ve );
	}

	return ve;
}

function mouseEventCallback( event ) {
	var touchID = $.data(event.target, touchTargetPropertyName);

	if ( !blockMouseTriggers && ( !lastTouchID || lastTouchID !== touchID ) ){
		var ve = triggerVirtualEvent( "v" + event.type, event );
		if ( ve ) {
			if ( ve.isDefaultPrevented() ) {
				event.preventDefault();
			}
			if ( ve.isPropagationStopped() ) {
				event.stopPropagation();
			}
			if ( ve.isImmediatePropagationStopped() ) {
				event.stopImmediatePropagation();
			}
		}
	}
}

function handleTouchStart( event ) {

	var touches = getNativeEvent( event ).touches,
		target, flags;

	if ( touches && touches.length === 1 ) {

		target = event.target;
		flags = getVirtualBindingFlags( target );

		if ( flags.hasVirtualBinding ) {

			lastTouchID = nextTouchID++;
			$.data( target, touchTargetPropertyName, lastTouchID );

			clearResetTimer();

			disableMouseBindings();
			didScroll = false;

			var t = getNativeEvent( event ).touches[ 0 ];
			startX = t.pageX;
			startY = t.pageY;

			triggerVirtualEvent( "vmouseover", event, flags );
			triggerVirtualEvent( "vmousedown", event, flags );
		}
	}
}

function handleScroll( event ) {
	if ( blockTouchTriggers ) {
		return;
	}

	if ( !didScroll ) {
		triggerVirtualEvent( "vmousecancel", event, getVirtualBindingFlags( event.target ) );
	}

	didScroll = true;
	startResetTimer();
}

function handleTouchMove( event ) {
	if ( blockTouchTriggers ) {
		return;
	}

	var t = getNativeEvent( event ).touches[ 0 ],
		didCancel = didScroll,
		moveThreshold = $.vmouse.moveDistanceThreshold;
		didScroll = didScroll ||
			( Math.abs(t.pageX - startX) > moveThreshold ||
				Math.abs(t.pageY - startY) > moveThreshold ),
		flags = getVirtualBindingFlags( event.target );

	if ( didScroll && !didCancel ) {
		triggerVirtualEvent( "vmousecancel", event, flags );
	}

	triggerVirtualEvent( "vmousemove", event, flags );
	startResetTimer();
}

function handleTouchEnd( event ) {
	if ( blockTouchTriggers ) {
		return;
	}

	disableTouchBindings();

	var flags = getVirtualBindingFlags( event.target ),
		t;
	triggerVirtualEvent( "vmouseup", event, flags );

	if ( !didScroll ) {
		var ve = triggerVirtualEvent( "vclick", event, flags );
		if ( ve && ve.isDefaultPrevented() ) {
			// The target of the mouse events that follow the touchend
			// event don't necessarily match the target used during the
			// touch. This means we need to rely on coordinates for blocking
			// any click that is generated.
			t = getNativeEvent( event ).changedTouches[ 0 ];
			clickBlockList.push({
				touchID: lastTouchID,
				x: t.clientX,
				y: t.clientY
			});

			// Prevent any mouse events that follow from triggering
			// virtual event notifications.
			blockMouseTriggers = true;
		}
	}
	triggerVirtualEvent( "vmouseout", event, flags);
	didScroll = false;

	startResetTimer();
}

function hasVirtualBindings( ele ) {
	var bindings = $.data( ele, dataPropertyName ),
		k;

	if ( bindings ) {
		for ( k in bindings ) {
			if ( bindings[ k ] ) {
				return true;
			}
		}
	}
	return false;
}

function dummyMouseHandler(){}

function getSpecialEventObject( eventType ) {
	var realType = eventType.substr( 1 );

	return {
		setup: function( data, namespace ) {
			// If this is the first virtual mouse binding for this element,
			// add a bindings object to its data.

			if ( !hasVirtualBindings( this ) ) {
				$.data( this, dataPropertyName, {});
			}

			// If setup is called, we know it is the first binding for this
			// eventType, so initialize the count for the eventType to zero.
			var bindings = $.data( this, dataPropertyName );
			bindings[ eventType ] = true;

			// If this is the first virtual mouse event for this type,
			// register a global handler on the document.

			activeDocHandlers[ eventType ] = ( activeDocHandlers[ eventType ] || 0 ) + 1;

			if ( activeDocHandlers[ eventType ] === 1 ) {
				$document.bind( realType, mouseEventCallback );
			}

			// Some browsers, like Opera Mini, won't dispatch mouse/click events
			// for elements unless they actually have handlers registered on them.
			// To get around this, we register dummy handlers on the elements.

			$( this ).bind( realType, dummyMouseHandler );

			// For now, if event capture is not supported, we rely on mouse handlers.
			if ( eventCaptureSupported ) {
				// If this is the first virtual mouse binding for the document,
				// register our touchstart handler on the document.

				activeDocHandlers[ "touchstart" ] = ( activeDocHandlers[ "touchstart" ] || 0) + 1;

				if (activeDocHandlers[ "touchstart" ] === 1) {
					$document.bind( "touchstart", handleTouchStart )
						.bind( "touchend", handleTouchEnd )

						// On touch platforms, touching the screen and then dragging your finger
						// causes the window content to scroll after some distance threshold is
						// exceeded. On these platforms, a scroll prevents a click event from being
						// dispatched, and on some platforms, even the touchend is suppressed. To
						// mimic the suppression of the click event, we need to watch for a scroll
						// event. Unfortunately, some platforms like iOS don't dispatch scroll
						// events until *AFTER* the user lifts their finger (touchend). This means
						// we need to watch both scroll and touchmove events to figure out whether
						// or not a scroll happenens before the touchend event is fired.

						.bind( "touchmove", handleTouchMove )
						.bind( "scroll", handleScroll );
				}
			}
		},

		teardown: function( data, namespace ) {
			// If this is the last virtual binding for this eventType,
			// remove its global handler from the document.

			--activeDocHandlers[ eventType ];

			if ( !activeDocHandlers[ eventType ] ) {
				$document.unbind( realType, mouseEventCallback );
			}

			if ( eventCaptureSupported ) {
				// If this is the last virtual mouse binding in existence,
				// remove our document touchstart listener.

				--activeDocHandlers[ "touchstart" ];

				if ( !activeDocHandlers[ "touchstart" ] ) {
					$document.unbind( "touchstart", handleTouchStart )
						.unbind( "touchmove", handleTouchMove )
						.unbind( "touchend", handleTouchEnd )
						.unbind( "scroll", handleScroll );
				}
			}

			var $this = $( this ),
				bindings = $.data( this, dataPropertyName );

			// teardown may be called when an element was
			// removed from the DOM. If this is the case,
			// jQuery core may have already stripped the element
			// of any data bindings so we need to check it before
			// using it.
			if ( bindings ) {
				bindings[ eventType ] = false;
			}

			// Unregister the dummy event handler.

			$this.unbind( realType, dummyMouseHandler );

			// If this is the last virtual mouse binding on the
			// element, remove the binding data from the element.

			if ( !hasVirtualBindings( this ) ) {
				$this.removeData( dataPropertyName );
			}
		}
	};
}

// Expose our custom events to the jQuery bind/unbind mechanism.

for ( var i = 0; i < virtualEventNames.length; i++ ){
	$.event.special[ virtualEventNames[ i ] ] = getSpecialEventObject( virtualEventNames[ i ] );
}

// Add a capture click handler to block clicks.
// Note that we require event capture support for this so if the device
// doesn't support it, we punt for now and rely solely on mouse events.
if ( eventCaptureSupported ) {
	document.addEventListener( "click", function( e ){
		var cnt = clickBlockList.length,
			target = e.target,
			x, y, ele, i, o, touchID;

		if ( cnt ) {
			x = e.clientX;
			y = e.clientY;
			threshold = $.vmouse.clickDistanceThreshold;

			// The idea here is to run through the clickBlockList to see if
			// the current click event is in the proximity of one of our
			// vclick events that had preventDefault() called on it. If we find
			// one, then we block the click.
			//
			// Why do we have to rely on proximity?
			//
			// Because the target of the touch event that triggered the vclick
			// can be different from the target of the click event synthesized
			// by the browser. The target of a mouse/click event that is syntehsized
			// from a touch event seems to be implementation specific. For example,
			// some browsers will fire mouse/click events for a link that is near
			// a touch event, even though the target of the touchstart/touchend event
			// says the user touched outside the link. Also, it seems that with most
			// browsers, the target of the mouse/click event is not calculated until the
			// time it is dispatched, so if you replace an element that you touched
			// with another element, the target of the mouse/click will be the new
			// element underneath that point.
			//
			// Aside from proximity, we also check to see if the target and any
			// of its ancestors were the ones that blocked a click. This is necessary
			// because of the strange mouse/click target calculation done in the
			// Android 2.1 browser, where if you click on an element, and there is a
			// mouse/click handler on one of its ancestors, the target will be the
			// innermost child of the touched element, even if that child is no where
			// near the point of touch.

			ele = target;

			while ( ele ) {
				for ( i = 0; i < cnt; i++ ) {
					o = clickBlockList[ i ];
					touchID = 0;

					if ( ( ele === target && Math.abs( o.x - x ) < threshold && Math.abs( o.y - y ) < threshold ) ||
								$.data( ele, touchTargetPropertyName ) === o.touchID ) {
						// XXX: We may want to consider removing matches from the block list
						//      instead of waiting for the reset timer to fire.
						e.preventDefault();
						e.stopPropagation();
						return;
					}
				}
				ele = ele.parentNode;
			}
		}
	}, true);
}
})( jQuery, window, document );

$.extend( $.support, {
	orientation: "orientation" in window && "onorientationchange" in window,
	touch: "ontouchend" in document
});

(function( $, window, undefined ) {

$.attrFn=$.attrFn || {};

// add new event shortcuts
$.each( ( "touchstart touchmove touchend orientationchange throttledresize " +
					"tap taphold swipe swipeleft swiperight scrollstart scrollstop" ).split( " " ), function( i, name ) {

	$.fn[ name ] = function( fn ) {
		return fn ? this.bind( name, fn ) : this.trigger( name );
	};

	$.attrFn[ name ] = true;
});

var supportTouch = $.support.touch,
	scrollEvent = "touchmove scroll",
	touchStartEvent = supportTouch ? "touchstart" : "mousedown",
	touchStopEvent = supportTouch ? "touchend" : "mouseup",
	touchMoveEvent = supportTouch ? "touchmove" : "mousemove";

function triggerCustomEvent( obj, eventType, event ) {
	var originalType = event.type;
	event.type = eventType;
	$.event.handle.call( obj, event );
	event.type = originalType;
}

// also handles taphold
$.event.special.tap = {
	setup: function() {
		var thisObject = this,
			$this = $( thisObject );

		$this.bind( "vmousedown", function( event ) {

			if ( event.which && event.which !== 1 ) {
				return false;
			}

			var origTarget = event.target,
				origEvent = event.originalEvent,
				timer;

			function clearTapTimer() {
				clearTimeout( timer );
			}

			function clearTapHandlers() {
				clearTapTimer();

				$this.unbind( "vclick", clickHandler )
					.unbind( "vmouseup", clearTapTimer );
				$( document ).unbind( "vmousecancel", clearTapHandlers );
			}

			function clickHandler(event) {
				clearTapHandlers();

				// ONLY trigger a 'tap' event if the start target is
				// the same as the stop target.
				if ( origTarget == event.target ) {
					triggerCustomEvent( thisObject, "tap", event );
				}
			}

			$this.bind( "vmouseup", clearTapTimer )
				.bind( "vclick", clickHandler );
			$( document ).bind( "vmousecancel", clearTapHandlers );

			timer = setTimeout(function() {
					triggerCustomEvent( thisObject, "taphold", $.Event( "taphold", { target: origTarget } ) );
			}, 750 );
		});
	}
};

// also handles swipeleft, swiperight
$.event.special.swipe = {
	scrollSupressionThreshold: 10, // More than this horizontal displacement, and we will suppress scrolling.

	durationThreshold: 1000, // More time than this, and it isn't a swipe.

	horizontalDistanceThreshold: 30,  // Swipe horizontal displacement must be more than this.

	verticalDistanceThreshold: 75,  // Swipe vertical displacement must be less than this.

	setup: function() {
		var thisObject = this,
			$this = $( thisObject );

		$this.bind( touchStartEvent, function( event ) {
			var data = event.originalEvent.touches ?
								event.originalEvent.touches[ 0 ] : event,
				start = {
					time: ( new Date() ).getTime(),
					coords: [ data.pageX, data.pageY ],
					origin: $( event.target )
				},
				stop;

			function moveHandler( event ) {

				if ( !start ) {
					return;
				}

				var data = event.originalEvent.touches ?
						event.originalEvent.touches[ 0 ] : event;

				stop = {
					time: ( new Date() ).getTime(),
					coords: [ data.pageX, data.pageY ]
				};

				// prevent scrolling
				if ( Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.scrollSupressionThreshold ) {
					event.preventDefault();
				}
			}

			$this.bind( touchMoveEvent, moveHandler )
				.one( touchStopEvent, function( event ) {
					$this.unbind( touchMoveEvent, moveHandler );
					if ( start && stop ) {
						if ( stop.time - start.time < $.event.special.swipe.durationThreshold &&
								Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.horizontalDistanceThreshold &&
								Math.abs( start.coords[ 1 ] - stop.coords[ 1 ] ) < $.event.special.swipe.verticalDistanceThreshold ) {

							start.origin.trigger( "swipe" )
								.trigger( start.coords[0] > stop.coords[ 0 ] ? "swipeleft" : "swiperight" );
						}
					}
					start = stop = undefined;
				});
		});
	}
};



$.each({
	scrollstop: "scrollstart",
	taphold: "tap",
	swipeleft: "swipe",
	swiperight: "swipe"
}, function( event, sourceEvent ) {

	$.event.special[ event ] = {
		setup: function() {
			$( this ).bind( sourceEvent, $.noop );
		}
	};
});

})( jQuery, this );

/*
 * @author			Emanuel Vitzthum
 * @copyright		© 2011 jQuery SDK v1.3.1
 * @info			http://www.jquerysdk.com
 *
 * @license			Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL (http://www.gnu.org/licenses/gpl.html)
 *
 * @plugin			jQuery.event.fix
 */

(function( $,window, undefined ){

	var _fix = $.event.fix;

	$.extend( $.event, {
		fix: function( event ) {
			var originalEvent;
			
			// run  the original fix method
			event = _fix.call( $.event, event );

			// get the original event
			if( !(originalEvent = event.originalEvent) )
				return event;

			// Add originalTarget, if necessary
			if ( !event.originalTarget )
				event.originalTarget = originalEvent.srcElement || event.target;

			// be sure touches isn't undefined
			// Calculate pageX/Y if missing and touches[0] available
			if( (event.touches = originalEvent.touches || {length:0}).length ){
				event.pageX = event.touches[0].pageX;
				event.pageY = event.touches[0].pageY;
			}
			// Calculate pageX/Y and touches[0] if missing and touch available
			else if( originalEvent.touch ){
				event.touches = { 0: originalEvent.touch, length: 1 };
				event.pageX = originalEvent.touch.pageX;
				event.pageY = originalEvent.touch.pageY;
			}

			return event;
		}
	});

})( jQuery, this );

/*
 * @author			Emanuel Vitzthum
 * @copyright		© 2011 jQuery SDK v1.3.1
 * @info			http://www.jquerysdk.com
 *
 * @license			Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL (http://www.gnu.org/licenses/gpl.html)
 *
 * @plugin			jQuery.event.special.touch
 */

(function( $,window, undefined ){

	var html = $("html"),

		namespaceDrag = "_specialTouchdrag",

		namespacePinch = "_specialTouchpinch",

		namespacePinchX = "_specialTouchpinchInOut",

		namespaceSwipe = "_specialTouchswipe",

		handlerPinchX = function(){},

		handlerSwipe = function(){},
		
		// get the pinch direction by delta pinch value
		getPinch = function( delta, oldDelta, min ){
			return 	min && Math.abs(delta) < min ? false : !delta ? oldDelta : (delta >= 0 ? 'in' : 'out');
		},
		

		// get the swipe direction by delta drag values
		getSwipe = function( deltaX, deltaY, min ){
			var absX = Math.abs(deltaX),
				absY = Math.abs(deltaY);
			
			return (Math.abs(absX - absY) <= 1) || !(!min || absX > min || absY > min) ? false : (absX > absY ? (deltaX < 0 ? "left" : "right") : (deltaY < 0 ? "up" : "down"));
		},

		
		// get/set the id for element + handler
		getId = function( node, obj ){
			return ( node[ $.expando ] || (node[ $.expando ] = ++$.guid) )+"."+( obj.guid || (obj.guid = ++$.guid) );
		},
		
		// check target (may it's a text element)
		isTarget = function( target, originalTarget ){
			var fixedTarget = target.nodeType === 3 ? target.parentNode : target;

			return fixedTarget===originalTarget || $.contains(originalTarget, fixedTarget);
		},

		touchstart = function( originalBind, originalHandler, originalTarget, namespace, id ){
			return (touchstart[ id ] = function(event){
					var touches = event.touches,
						isDrag	= originalBind.type==="touchdrag" && touches.length === 1 &&
									isTarget(event.originalTarget, originalTarget),
						isPinch	= originalBind.type==="touchpinch" &&  touches.length === 2 &&
									isTarget(touches[0].target, originalTarget) &&
									isTarget(touches[1].target, originalTarget),
						cache;

					// setup params
					if( isDrag || isPinch ){
						// bind move / end events
						html.on( "touchmove."+namespace, touchmove( originalBind, originalHandler, originalTarget, namespace, id ) )
							.on( "touchend."+namespace, touchend( originalBind, originalHandler, originalTarget, namespace, id ) )
							.on( "touchcancel."+namespace, touchcancel( originalBind, originalHandler, originalTarget, namespace, id ) );
						
						cache = touchmove[ id ];
						cache.direction = true;
						cache.state = "start";
						cache.deltaT = event.timeStamp;
					}
					// TOUCHDRAG setup params
					if( isDrag ){
						cache.startT = cache.deltaT;
						cache.startX = (cache.deltaX = event.pageX) + 0;
						cache.startY = (cache.deltaY = event.pageY) + 0;
					}
					// TOUCHZOOM setup params
					if( isPinch ){
						cache.delta  = Math.sqrt(
							Math.pow( cache.startX = (cache.deltaX = Math.abs( touches[0].pageX - touches[1].pageX )) + 0, 2 ) +
							Math.pow( cache.startY = (cache.deltaY = Math.abs( touches[0].pageY - touches[1].pageY )) + 0, 2 )
						);
						cache.start	= cache.delta + 0;
					}
				});
		},

		touchmove = function( originalBind, originalHandler, originalTarget, namespace, id ){
			return (touchmove[ id ] = function(event){
					var touches = event.touches,
						isDrag	= originalBind.type==="touchdrag",
						isPinch = originalBind.type==="touchpinch" && touches.length >= 2,
						cache	= touchmove[ id ],
						state,
						deltaT,
						deltaX,
						deltaY,
						delta,
						direction,
						_direction;

					// TOUCHDRAG modify event params
					if( isDrag ){
						state		= event.dragState  = cache.state;
						deltaT		= event.dragDeltaT = - cache.deltaT + (cache.deltaT = event.timeStamp);
						deltaX		= event.dragDeltaX = - cache.deltaX + (cache.deltaX = event.pageX);
						deltaY		= event.dragDeltaY = - cache.deltaY + (cache.deltaY = event.pageY);
						delta		= event.dragDelta  = Math.sqrt( Math.pow(deltaX,2) + Math.pow(deltaY,2) );
						_direction	= cache.direction;
						direction	= getSwipe( deltaX, deltaY );
					}
					// TOUCHZOOM modify event params
					if( isPinch ){
						event.pinchState	= cache.state;
						event.pinchDeltaT	= - cache.deltaT + (cache.deltaT = event.timeStamp);
						event.pinchDeltaX	= - cache.deltaX + (cache.deltaX = deltaX = Math.abs( touches[0].pageX - touches[1].pageX ) );
						event.pinchDeltaY	= - cache.deltaY + (cache.deltaY = deltaY = Math.abs( touches[0].pageY - touches[1].pageY ) );
						event.pinchDelta	= - cache.delta  + (cache.delta  = delta  = Math.sqrt( Math.pow(deltaX,2) + Math.pow(deltaY,2) ) );
						direction			= getPinch( event.pinchDelta, _direction = cache.direction );
					}

					// trigger
					if( isDrag || isPinch ){
						event.type = originalBind.type;
						event.target = originalTarget;
						event.data = originalBind.data;
						cache.state = "while";
						cache.direction = direction && _direction ? _direction !== true ? (direction == _direction ? direction : false) : direction : _direction;

						// $.event.trigger( event, originalBind.data, originalTarget, true );
						// doesn't work because it prevents default
						originalHandler.apply(originalTarget,arguments);
					}
				});
		},

		touchend = function( originalBind, originalHandler, originalTarget, namespace, id ){
			return (touchend[ id ] = function(event){if( touchmove[ id ] ){
					// unbind events
					html.off( "touchmove."+namespace, touchmove[ id ] )
						.off( "touchend."+namespace, touchend[ id ] )
						.off( "touchcancel."+namespace, touchend[ id ] );

					var isDrag	= originalBind.type==="touchdrag",
						isPinch = originalBind.type==="touchpinch",
						cache	= touchmove[ id ],
						state,
						deltaT,
						deltaX,
						deltaY,
						delta,
						direction,
						_direction;
						
					// TOUCHDRAG modify event params
					if( isDrag ){
						event.dragState = cache.state = "stop";
						event.dragDeltaT = - cache.deltaT + event.timeStamp;
						event.dragDeltaX = 0;
						event.dragDeltaY = 0;
						event.dragDelta  = 0;
					}
					// TOUCHZOOM modify event params
					if( isPinch ){
						event.pinchState = cache.state = "stop";
						event.pinchDeltaT = - cache.deltaT + event.timeStamp;
						event.pinchDeltaX = 0;
						event.pinchDeltaY = 0;
						event.pinchDelta  = 0;
					}
					// trigger
					if( isDrag || isPinch ){
						event.type = originalBind.type;
						event.target = originalTarget;
						event.data = originalBind.data;

						//$.event.trigger( event, originalBind.data, originalTarget, true );
						// doesn't work because it prevents default
						originalHandler.apply(originalTarget,arguments);
					}
					// TOUCHSWIPE modify event params
					if( isDrag && namespace == namespaceSwipe ){
						delete( event.dragState );
						
						deltaT = event.dragDeltaT = - cache.startT + event.timeStamp;
						deltaX = event.dragDeltaX = - cache.startX + cache.deltaX;
						deltaY = event.dragDeltaY = - cache.startY + cache.deltaY;
						_direction	= cache.direction;
						direction = getSwipe( deltaX, deltaY, 20 );

						if( direction && _direction == direction ){
							event.type = "touchswipe" + direction;
							$(originalTarget).trigger(event);
						}
					}
					// TOUCHPINCH IN / OUT modify event params
					if( isPinch && namespace == namespacePinchX ){
						delete( event.pinchState );
						
						deltaT = event.pinchDeltaT	= - cache.startT + event.timeStamp;
						deltaX = event.pinchDeltaX	= - cache.startX + cache.deltaX;
						deltaY = event.pinchDeltaY	= - cache.startY + cache.deltaY;
						delta  = event.pinchDelta	= - cache.start  + cache.delta;
						direction = getPinch( delta, _direction	= cache.direction, 80 );
						
						if( direction && _direction == direction ){
							event.type = "touchpinch" + direction;
							$(originalTarget).trigger(event);
						}
					}

					delete( touchmove[ id ] );
					delete( touchend[ id ] );
				}});
		},

		touchcancel = touchend;

	// touchdrag, touchpinch
	$.each({
		touchdrag: namespaceDrag,
		touchpinch: namespacePinch
	}, function( name, namespace ) {

		$.event.special[ name ] = {
			// don't add real event
			setup	: $.noop,
			// add special handler
			add		: function( bind ) {
				namespace = bind.namespace === namespaceSwipe ? namespaceSwipe : bind.namespace === namespacePinchX ? namespacePinchX : namespace;
				$(this).on( "touchstart."+namespace, touchstart( bind, bind.handler, this, namespace, getId(this,bind) ) );
			},
			// remove special handler
			remove	: function( unbind ) {
				var id = getId(this,unbind),
					handleStart = touchstart[ id ],
					handleMove = touchmove[ id ];

				namespace = unbind.namespace === namespaceSwipe ? namespaceSwipe : unbind.namespace === namespacePinchX ? namespacePinchX : namespace;

				$(this).off( "touchstart."+namespace, handleStart );
				delete( handleStart );

				html.off( "touchmove."+namespace, handleMove );
				delete( handleMove );
			}
		};

	});

	// touchpinchin, touchpinchout
	$.each({
		'in'	: namespacePinchX,
		out		: namespacePinchX
	}, function( name, namespace ){

		$.event.special[ "touchpinch"+name ] = {
			// don't add real event
			setup	: $.noop,
			// add special handler
			add		: function( bind ) {
				if( !touchstart[ getId(this,handlerPinchX) ] )
					$(this).on("touchpinch."+namespace,handlerPinchX);
			}
		};

	});

	// touchswipeup, touchswiperight,touchswipedown,touchswipeleft
	$.each({
		up		: namespaceSwipe,
		right	: namespaceSwipe,
		down	: namespaceSwipe,
		left	: namespaceSwipe
	}, function( name, namespace ){

		$.event.special[ "touchswipe"+name ] = {
			// don't add real event
			setup	: $.noop,
			// add special handler
			add		: function( bind ) {
				if( !touchstart[ getId(this,handlerSwipe) ] )
					$(this).on("touchdrag."+namespace,handlerSwipe);
			}
		};

	});
	
	// DEPRECATED: touchzoom
	var zoom = {},
		zoomMap = "State DeltaT DeltaX DeltaY Delta".split(" ");
	$.event.special.touchzoom = {
		// don't add real event
		setup	: $.noop,
		// add special handler
		add		: function( bind ) {
			$.debug.warn("METHOD DEPRECATED: 'touchzoom' event is replaced by 'touchpinch' event");
			
			$(this)
				.on("touchpinch."+namespacePinch, (
					zoom[ getId(this,bind) ] = function(event){
						for( var i=0,l=zoomMap.length ; i<l ; i++ ){
							event[ 'zoom'+zoomMap[ i ] ] = event[ 'pinch'+zoomMap[ i ] ];
							delete event[ 'pinch'+zoomMap[ i ] ];
						}
						event.type = 'touchzoom';
						bind.handler.apply(this,arguments);
					})
				);
		},
		// remove special handler
		remove	: function( unbind ) {
			var id = getId(this,unbind);
			
			$(this).off("touchpinch."+namespacePinc, zoom[ id ] );
			delete zoom[ id ];
		}
	};
	 

})( jQuery, this );
